/**
 * Created by IntelliJ IDEA.
 * User: I053631
 * Date: 04/08/11
 * Time: 14:43
 * To change this template use File | Settings | File Templates.
 */
package com.sfdk.graphics.engine.data.layers {
import com.sfdk.graphics.engine.api.IMapObject;
import com.sfdk.graphics.engine.api.ITileset;
import com.sfdk.graphics.engine.core.enums.LayerType;
import com.sfdk.graphics.engine.data.TileInfo;
import com.sfdk.log.Logger;
import com.sfdk.utils.Base64;
import com.sfdk.utils.IntPoint;

import flash.utils.ByteArray;

public class EditableLayer extends Layer implements IEditableLayer {
    public function EditableLayer(layerData:XML, associatedMap:IMapObject, layerType:LayerType) {
        super(layerData, associatedMap, layerType);
    }

    override protected function parseLayerData():void {
        _tileData = new Vector.<Vector.<TileInfo>>();

        var x:int;
        var y:int;
        var totalMapHeight:int = _associatedMap.totalHeightInTile;
        var totalMapWidth:int = _associatedMap.totalWidthInTile;

        if (_layerData == null) {
            // No layer data to parse, we have to built it from scratch
            for (y = 0; y < totalMapHeight; y++) {
                    _tileData[y] = new Vector.<TileInfo>();
                for (x = 0; x < totalMapWidth; x++) {
                    _tileData[y][x] = null;
                }
            }
        }
        else {
            if (_layerData.data[0] == null) {
                Logger.error("Layer :: Layer <data> tag enclosed no data. (empty data tag)");
            }
            else {
                // We store the layer name
                _name = _layerData.@name;

                var tilesetID:int = 0;
                var tileX:int = 0;
                var tileY:int = 0;
                var encodedValue:String = _layerData.data[0];
                var decodedByteArray:ByteArray = Base64.decode(encodedValue);
                var outputByteArray:ByteArray;
                var calculatedIndex:int;

                decodedByteArray.uncompress();

                outputByteArray = decodedByteArray;

                for (y = 0; y < totalMapHeight; y++) {
                    _tileData[y] = new Vector.<TileInfo>();
                    for (x = 0; x < totalMapWidth; x++) {
                        tilesetID = 0;
                        tileX = 0;
                        tileY = 0;

                        calculatedIndex = y * totalMapWidth;
                        calculatedIndex += x;
                        calculatedIndex *= 6;

                        tilesetID |= (outputByteArray[calculatedIndex]);
                        tilesetID |= (outputByteArray[calculatedIndex + 1] << 8 );

                        tileX |= (outputByteArray[calculatedIndex + 2]);
                        tileX |= (outputByteArray[calculatedIndex + 3] << 8 );

                        tileY |= (outputByteArray[calculatedIndex + 4]);
                        tileY |= (outputByteArray[calculatedIndex + 5] << 8 );

                        if (tilesetID == 0 || tileX < 0 || tileY < 0) {
                            _tileData[y][x] = null;
                        }
                        else {
                            var tileset:ITileset = _associatedMap.getTileset(tilesetID);
                            if (tileset != null)
                                _tileData[y][x] = new TileInfo(tileset, tileX, tileY);
                            else
                                _tileData[y][x] = null;
                        }
                    }
                }
            }
        }
    }

    private var _tempTiles:Vector.<Object> = new Vector.<Object>();

    public function setTiles(tiles:Vector.<Vector.<TileInfo>>, point:IntPoint):Vector.<IntPoint> {
        var modifiedTiles:Vector.<IntPoint> = new Vector.<IntPoint>();
        clearHover(modifiedTiles);

        var newInfo:TileInfo;
        var newTileInfo:TileInfo;

        for (var i:int = 0; i < tiles.length; i++) {
            var subTiles:Vector.<TileInfo> = tiles[i];
            for (var j:int = 0; j < subTiles.length; j++) {
                newInfo = subTiles[j];
                if (point.y + j >= 0 && point.y + j < _tileData.length && point.x + i >= 0 && point.x + i < _tileData[0].length) {

                    newTileInfo = new TileInfo(newInfo.tileset, newInfo.tileX, newInfo.tileY);
                    _tileData[point.y + j][point.x + i] = newTileInfo;

                    modifiedTiles.push(new IntPoint(point.x + i, point.y + j));
                }
            }
        }
        return modifiedTiles;
    }

    public function tempSetTiles(tiles:Vector.<Vector.<TileInfo>>, point:IntPoint):Vector.<IntPoint> {
        var modifiedTiles:Vector.<IntPoint> = new Vector.<IntPoint>();
        clearHover(modifiedTiles);

        // On stocke les infos précédentes
        var currTileselect:TileInfo;
        var newTileselect:TileInfo;
        var newTileInfo:TileInfo;
        var currentTileInfo:TileInfo;

        for (var i:int = 0; i < tiles.length; i++) {
            var subTiles:Vector.<TileInfo> = tiles[i];
            for (var j:int = 0; j < subTiles.length; j++) {
                currTileselect = subTiles[j];
                if (currTileselect != null && point.y + j >= 0 && point.y + j < _tileData.length && point.x + i >= 0 && point.x + i < _tileData[0].length) {
                    currentTileInfo = _tileData[point.y + j][point.x + i];
                    if (currentTileInfo != null)
                        newTileselect = new TileInfo(currentTileInfo.tileset, currentTileInfo.tileX, currentTileInfo.tileY);
                    else
                        newTileselect = null;
                    var targetPoint:IntPoint = new IntPoint(point.x + i, point.y + j);
                    _tempTiles.push({tileInfo : newTileselect, point : targetPoint});

                    newTileInfo = new TileInfo(currTileselect.tileset, currTileselect.tileX, currTileselect.tileY);
                    _tileData[point.y + j][point.x + i] = newTileInfo;

                    modifiedTiles.push(new IntPoint(point.x + i, point.y + j));
                }
            }
        }

        return modifiedTiles;
    }

    public function fillTile(fillingTile:TileInfo, point:IntPoint):Vector.<IntPoint> {
        var convexPoint:IntPoint;
        var modifiedTiles:Vector.<IntPoint> = new Vector.<IntPoint>();
        var newTileInfo:TileInfo;

        clearHover(modifiedTiles);

        var convexedData:Vector.<IntPoint> = convexifie(_tileData, point);

        for (var j:int = 0; j < convexedData.length; j++) {
            convexPoint = convexedData[j];

            newTileInfo = new TileInfo(fillingTile.tileset, fillingTile.tileX, fillingTile.tileY);
            _tileData[convexPoint.y][convexPoint.x] = newTileInfo;

            modifiedTiles.push(new IntPoint(convexPoint.x, convexPoint.y));
        }
        return modifiedTiles;
    }

    public function tempFillTile(fillingTile:TileInfo, point:IntPoint, previousPoint:IntPoint):Vector.<IntPoint> {
        var currentTileInfo:TileInfo;
        var convexPoint:IntPoint;
        var previousTileInfo:TileInfo;
        var modifiedTiles:Vector.<IntPoint> = new Vector.<IntPoint>();
        var newTileInfo:TileInfo;

        if (point.y < _tileData.length && point.y < _tileData[0].length) {
            currentTileInfo = _tileData[point.y][point.x];
            previousTileInfo = _tileData[previousPoint.y][previousPoint.x];

            if ((!(currentTileInfo == null && previousTileInfo == null)) ||
                    (currentTileInfo != null && !currentTileInfo.equals(previousTileInfo)) &&
                            (currentTileInfo.tileset != fillingTile.tileset || currentTileInfo.tileX != fillingTile.tileX || currentTileInfo.tileY != fillingTile.tileY)) {
                clearHover(modifiedTiles);

                var convexedData:Vector.<IntPoint> = convexifie(_tileData, point);
                var targetPoint:IntPoint;

                for (var j:int = 0; j < convexedData.length; j++) {
                    convexPoint = convexedData[j];
                    currentTileInfo = _tileData[convexPoint.y][convexPoint.x];

                    if (currentTileInfo != null)
                        previousTileInfo = new TileInfo(currentTileInfo.tileset, currentTileInfo.tileX, currentTileInfo.tileY);
                    else
                        previousTileInfo = null;
                    targetPoint = new IntPoint(convexPoint.x, convexPoint.y);
                    _tempTiles.push({tileInfo :previousTileInfo,  point : targetPoint});

                    newTileInfo = new TileInfo(fillingTile.tileset, fillingTile.tileX, fillingTile.tileY);
                    _tileData[convexPoint.y][convexPoint.x] = newTileInfo;

                    modifiedTiles.push(new IntPoint(convexPoint.x, convexPoint.y));
                }
            }
        }
        return modifiedTiles;
    }

    private function convexifie(map:Vector.<Vector.<TileInfo>>, initPoint:IntPoint):Vector.<IntPoint> {
        var taille1:int = map.length;
        var taille2:int = map[0].length;
        var i:int, j:int;
        var foret:Vector.<IntPoint> = new Vector.<IntPoint>();
        var foret2:Vector.<Vector.<Boolean>> = new Vector.<Vector.<Boolean>>();

        for (j = 0; j < taille1; j++) {
            foret2[j] = new Vector.<Boolean>();
            for (i = 0; i < taille2; i++) {
                foret2[j][i] = false;
            }
        }
        function remplissage(departx:int, departy:int, map:Vector.<Vector.<TileInfo>>, foret:Vector.<IntPoint>, foret2:Vector.<Vector.<Boolean>>):void {
            var pile:Vector.<IntPoint> = new Vector.<IntPoint>();
            var westPoint:IntPoint = new IntPoint();
            var eastPoint:IntPoint = new IntPoint();
            var tilevaleur:TileInfo = map[departy][departx];
            pile.push(new IntPoint(departx, departy));

            while (pile.length != 0) {
                var tmp:IntPoint = pile.pop();
                if (!foret2[tmp.y][tmp.x]) {
                    westPoint.x = tmp.x;
                    westPoint.y = tmp.y;

                    eastPoint.x = tmp.x;
                    eastPoint.y = tmp.y;

                    while (westPoint.x > 0 && !foret2[westPoint.y][westPoint.x] &&
                            (
                                    (tilevaleur == null && map[westPoint.y][westPoint.x - 1] == null) ||
                                            (tilevaleur != null && tilevaleur.equals(map[westPoint.y][westPoint.x - 1]))
                                    )
                            )
                        westPoint.x -= 1;

                    while (eastPoint.x < (map[0].length - 1) && !foret2[eastPoint.y][eastPoint.x] &&
                            (
                                    (tilevaleur == null && map[eastPoint.y][eastPoint.x] == null)
                                            || (tilevaleur != null && tilevaleur.equals(map[eastPoint.y][eastPoint.x + 1]))))
                        eastPoint.x += 1;

                    for (var i:int = westPoint.x; i <= eastPoint.x; i++) {
                        foret.push(new IntPoint(i, tmp.y));
                        foret2[tmp.y][i] = true;
                        if (tmp.y > 0 && ((tilevaleur == null && map[tmp.y - 1][i] == null) || (tilevaleur != null && tilevaleur.equals(map[tmp.y - 1][i]))))
                            pile.push(new IntPoint(i, tmp.y - 1));
                        if (tmp.y < (map.length - 1) && ((tilevaleur == null && map[tmp.y + 1][i] == null) || (tilevaleur != null && tilevaleur.equals(map[tmp.y + 1][i]))))
                            pile.push(new IntPoint(i, tmp.y + 1));
                    }
                }
            }
        }

        remplissage(initPoint.x, initPoint.y, map, foret, foret2);

        return foret;
    }

    public function clearHover(modifiedTiles:Vector.<IntPoint> = null):void {
        if (modifiedTiles == null)
            modifiedTiles = new Vector.<IntPoint>();
        var currTileselect:TileInfo;
        var currentPoint:IntPoint;
        var currentInfo:TileInfo;
        for (var i:int = 0; i < _tempTiles.length; i++) {
            currTileselect = _tempTiles[i].tileInfo;
            currentPoint = _tempTiles[i].point;
            currentInfo = _tileData[currentPoint.y][currentPoint.x];
            _tileData[currentPoint.y][currentPoint.x] = currTileselect;
            modifiedTiles.push(new IntPoint(currentPoint.x, currentPoint.y));
        }

        _tempTiles.splice(0, _tempTiles.length);
    }
}
}
